package edu.stanford.nlp.parser.ensemble.maltparser.parser;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;


import edu.stanford.nlp.parser.ensemble.maltparser.core.config.ConfigurationException;
import edu.stanford.nlp.parser.ensemble.maltparser.core.exception.MaltChainedException;
import edu.stanford.nlp.parser.ensemble.maltparser.core.symbol.SymbolTable;
import edu.stanford.nlp.parser.ensemble.maltparser.core.syntaxgraph.DependencyStructure;
import edu.stanford.nlp.parser.ensemble.maltparser.core.syntaxgraph.edge.Edge;
import edu.stanford.nlp.parser.ensemble.maltparser.core.syntaxgraph.node.DependencyNode;
import edu.stanford.nlp.parser.ensemble.maltparser.parser.guide.ClassifierGuide;
/**
 * @author Johan Hall
 *
 */
public abstract class Algorithm {
	protected DependencyParserConfig manager;
	protected ClassifierGuide classifierGuide;
	protected ParserState parserState;
	protected ParserConfiguration currentParserConfiguration;
	protected boolean diagnostics = false;
	protected BufferedWriter diaWriter;
	/**
	 * Creates a parsing algorithm
	 * 
	 * @param manager a reference to the single malt configuration
	 * @throws MaltChainedException
	 */
	public Algorithm(DependencyParserConfig manager) throws MaltChainedException {
		setManager(manager);
		setDiagnostics((Boolean)manager.getOptionValue("singlemalt", "diagnostics"));
		if (diagnostics) {
			openDiaWriter(manager.getOptionValue("singlemalt", "diafile").toString());
		}
	}
	
	public abstract void terminate() throws MaltChainedException;
	
	public boolean isDiagnostics() {
		return diagnostics;
	}

	public void setDiagnostics(boolean diagnostics) {
		this.diagnostics = diagnostics;
	}


	public BufferedWriter getDiaWriter() {
		return diaWriter;
	}
	
	public void writeToDiaFile(String message) throws MaltChainedException {
		try {
			getDiaWriter().write(message);
		} catch (IOException e) {
			throw new MaltChainedException("Could not write to the diagnostic file. ", e);
		}
	}
	
	public void closeDiaWriter() throws MaltChainedException {
		if (diaWriter != null) {
			try {
				diaWriter.flush();
				diaWriter.close();
			} catch (IOException e) {
				throw new MaltChainedException("Could not close the diagnostic file. ", e);
			}
		}
	}
	
	public void openDiaWriter(String fileName) throws MaltChainedException {
		if (diagnostics) {
			try {
				if (fileName.equals("stdout")) {
					diaWriter = new BufferedWriter(new OutputStreamWriter(System.out));
				} else if (fileName.equals("stderr")) {
					diaWriter = new BufferedWriter(new OutputStreamWriter(System.err));
				} else {
					diaWriter = new BufferedWriter(new FileWriter(fileName));
				}
			} catch (IOException e) {
				throw new MaltChainedException("Could not open the diagnostic file. ", e);
			}
		}
	}

	/**
	 * Returns the classifier guide.
	 * 
	 * @return the classifier guide
	 */
	public ClassifierGuide getGuide() {
		return classifierGuide;
	}
	
	/**
	 * Sets the classifier guide
	 * 
	 * @param guide a classifier guide
	 */
	public void setGuide(ClassifierGuide guide) {
		this.classifierGuide = guide;
	}

	/**
	 * Returns the current active parser configuration
	 * 
	 * @return the current active parser configuration
	 */
	public ParserConfiguration getCurrentParserConfiguration() {
		return currentParserConfiguration;
	}
	
	/**
	 * Sets the current parser configuration
	 * 
	 * @param currentParserConfiguration a parser configuration
	 */
	protected void setCurrentParserConfiguration(ParserConfiguration currentParserConfiguration) {
		this.currentParserConfiguration = currentParserConfiguration;
	}
	
	/**
	 * Returns the parser state
	 * 
	 * @return the parser state
	 */
	public ParserState getParserState() {
		return parserState;
	}
	
	/**
	 * Sets the parser state
	 * 
	 * @param parserState a parser state
	 */
	protected void setParserState(ParserState parserState) {
		this.parserState = parserState;
	}

	/**
	 * Creates a parser factory specified by the --singlemalt-parsing_algorithm option
	 * 
	 * @return a parser factory
	 * @throws MaltChainedException
	 */
	protected AbstractParserFactory makeParserFactory() throws MaltChainedException {
		Class<?> clazz = (Class<?>)manager.getOptionValue("singlemalt", "parsing_algorithm");
		try {	
			Class<?>[] params = new Class<?>[1];
			params[0] = edu.stanford.nlp.parser.ensemble.maltparser.parser.Algorithm.class;
			Object[] arguments = new Object[params.length];
			arguments[0] = this;
			Constructor<?> constructor = clazz.getConstructor(params);
			return (AbstractParserFactory)constructor.newInstance(arguments);
		} catch (NoSuchMethodException e) {
			throw new ConfigurationException("The parser factory '"+clazz.getName()+"' cannot be initialized. ", e);
		} catch (InstantiationException e) {
			throw new ConfigurationException("The parser factory '"+clazz.getName()+"' cannot be initialized. ", e);
		} catch (IllegalAccessException e) {
			throw new ConfigurationException("The parser factory '"+clazz.getName()+"' cannot be initialized. ", e);
		} catch (InvocationTargetException e) {
			throw new ConfigurationException("The parser factory '"+clazz.getName()+"' cannot be initialized. ", e);			
		}
	}
	
	protected void initParserState(int k) throws MaltChainedException {
		AbstractParserFactory parserFactory = makeParserFactory();
		((SingleMalt)manager).addRegistry(parserFactory.getClass(), parserFactory);
		parserState = new ParserState(this, parserFactory, k);
	}
	
	/**
	 * Returns the single malt configuration
	 * 
	 * @return the single malt configuration
	 */
	public DependencyParserConfig getManager() {
		return manager;
	}

	/**
	 * Sets the single malt configuration
	 * 
	 * @param manager a single malt configuration
	 */
	public void setManager(DependencyParserConfig manager) {
		this.manager = manager;
	}
	
	/**
	 * Copies the edges of the source dependency structure to the target dependency structure
	 * 
	 * @param source a source dependency structure
	 * @param target a target dependency structure
	 * @throws MaltChainedException
	 */
	protected void copyEdges(DependencyStructure source, DependencyStructure target) throws MaltChainedException {
		for (int index : source.getTokenIndices()) {
			DependencyNode snode = source.getTokenNode(index);
			
			if (snode.hasHead()) {
				Edge s = snode.getHeadEdge();
				Edge t = target.addDependencyEdge(s.getSource().getIndex(), s.getTarget().getIndex());
				
				for (SymbolTable table : s.getLabelTypes()) {
					t.addLabel(table, s.getLabelSymbol(table));
				}
			}
		}
	}
}
